﻿using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace mooSQL.data
{

    [TypeDescriptionProvider(typeof(DapperRowTypeDescriptionProvider))]
    internal sealed partial class DapperRow
    {
        private sealed class DapperRowTypeDescriptionProvider : TypeDescriptionProvider
        {
            public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance)
                => new DapperRowTypeDescriptor(instance);
            public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
                => new DapperRowTypeDescriptor(instance);
        }

        //// in theory we could implement this for zero-length results to bind; would require
        //// additional changes, though, to capture a table even when no rows - so not currently provided
        //internal sealed class DapperRowList : List<DapperRow>, ITypedList
        //{
        //    private readonly DapperTable _table;
        //    public DapperRowList(DapperTable table) { _table = table; }
        //    PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
        //    {
        //        if (listAccessors != null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty;

        //        return DapperRowTypeDescriptor.GetProperties(_table);
        //    }

        //    string ITypedList.GetListName(PropertyDescriptor[] listAccessors) => null;
        //}

        private sealed class DapperRowTypeDescriptor : ICustomTypeDescriptor
        {
            private readonly DapperRow _row;
            public DapperRowTypeDescriptor(object instance)
                => _row = (DapperRow)instance;

            AttributeCollection ICustomTypeDescriptor.GetAttributes()
                => AttributeCollection.Empty;

            string ICustomTypeDescriptor.GetClassName() => typeof(DapperRow).FullName;

            string ICustomTypeDescriptor.GetComponentName() => null;

            private static readonly TypeConverter s_converter = new ExpandableObjectConverter();
            TypeConverter ICustomTypeDescriptor.GetConverter() => s_converter;

            EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => null;

            PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => null;

            object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => null;

            EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty;

            EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty;

            internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row);
            internal static PropertyDescriptorCollection GetProperties(DapperTable table, IDictionary<string,object> row = null)
            {
                string[] names = table?.FieldNames;
                if (names is null || names.Length == 0) return PropertyDescriptorCollection.Empty;
                var arr = new PropertyDescriptor[names.Length];
                for (int i = 0; i < arr.Length; i++)
                {
                    var type = row != null && row.TryGetValue(names[i], out var value) && value != null
                        ? value.GetType() : typeof(object);
                    arr[i] = new RowBoundPropertyDescriptor(type, names[i], i);
                }
                return new PropertyDescriptorCollection(arr, true);
            }
            PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => GetProperties(_row);

            PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => GetProperties(_row);

            object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => _row;
        }

        private sealed class RowBoundPropertyDescriptor : PropertyDescriptor
        {
            private readonly Type _type;
            private readonly int _index;
            public RowBoundPropertyDescriptor(Type type, string name, int index) : base(name, null)
            {
                _type = type;
                _index = index;
            }
            public override bool CanResetValue(object component) => true;
            public override void ResetValue(object component) => ((DapperRow)component).Remove(_index);
            public override bool IsReadOnly => false;
            public override bool ShouldSerializeValue(object component) => ((DapperRow)component).TryGetValue(_index, out _);
            public override Type ComponentType => typeof(DapperRow);
            public override Type PropertyType => _type;
            public override object GetValue(object component)
                => ((DapperRow)component).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value;
            public override void SetValue(object component, object value)
                => ((DapperRow)component).SetValue(_index, value is DBNull ? null : value);
        }
    }
    
}
